/*
 * Created on Jul 9, 2004
 */
package edu.swri.swiftvis.filters;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.JTabbedPane;

import edu.swri.swiftvis.DataElement;
import edu.swri.swiftvis.DataSink;
import edu.swri.swiftvis.DataSource;
import edu.swri.swiftvis.Filter;
import edu.swri.swiftvis.GraphElement;
import edu.swri.swiftvis.GraphPanel;
import edu.swri.swiftvis.util.GraphLabelString;
import edu.swri.swiftvis.util.OutputInfoPanel;
import edu.swri.swiftvis.util.SourceInfoPanel;
import edu.swri.swiftvis.util.ThreadHandler;

/**
 * This is an abstract class that can help people who are creating new filters that can
 * have multiple inputs into them.
 * @author Mark Lewis
 */
public abstract class AbstractMultipleSourceFilter implements Filter {
    protected AbstractMultipleSourceFilter() {
        //System.out.println(getNumStreams());
        for(int i=0; i<getNumStreams(); ++i) {
            dataVect.add(new ArrayList<DataElement>());
        }
    }

    /**
     * This constructor is used by the copy method of subclasses.
     * @param c The filter we are copying.
     * @param l A list of other elements being copied.  Only links to these should go through.
     */
    protected AbstractMultipleSourceFilter(AbstractMultipleSourceFilter c,List<GraphElement> l) {
        dataVect=new ArrayList<ArrayList<DataElement>>();
        for(int i=0; i<c.dataVect.size(); ++i) {
            dataVect.add(new ArrayList<DataElement>(c.dataVect.get(i)));
        }
        inputVector=new ArrayList<DataSource>();
        for(DataSource ds:c.inputVector) {
            if(l.contains(ds)) inputVector.add(ds);
        }
        sinkVector=new ArrayList<DataSink>();
        for(DataSink ds:c.sinkVector) {
            if(l.contains(ds)) sinkVector.add(ds);
        }
        label=new GraphLabelString(c.label);
    }
    
	public boolean validInput(DataSource ds){ return true; }

	public void addInput(DataSource input){
		if(input==this) return;
		inputVector.add(input);
		abstractRedoAllElements();
		if(sip!=null) sip.redoSourceList();
		input.addOutput(this);
	}

	public void removeInput(DataSource input){
		inputVector.remove(input);
		abstractRedoAllElements();
		if(sip!=null) sip.redoSourceList();
		input.removeOutput(this);
	}
    
    public void moveUpInput(int index) {
        DataSource ds=inputVector.get(index);
        inputVector.set(index,inputVector.get(index-1));
        inputVector.set(index-1,ds);
        abstractRedoAllElements();
        if(sip!=null) sip.redoSourceList();        
    }

	public DataSource getSource(int which){ return inputVector.get(which); }

	public int getNumSources(){ return inputVector.size(); }

	public void sourceAltered(DataSource source){
		abstractRedoAllElements();
	}

	public void addOutput(DataSink sink){ sinkVector.add(sink); }

	public void removeOutput(DataSink sink){ sinkVector.remove(sink); }

    public int getNumOutputs() {
        return sinkVector.size();
    }
    
    public DataSink getOutput(int which) {
        return sinkVector.get(which);
    }

    public Rectangle getBounds() {
        return label.getBounds();
    }

    public void translate(int dx,int dy) {
        label.translate(dx,dy);
    }
    
    public void clearData() {
        dataVect=new ArrayList<ArrayList<DataElement>>();
        for(int i=0; i<getNumStreams(); ++i) {
            dataVect.add(new ArrayList<DataElement>());
        }
    }

	public Paint getPaint(){ return Color.blue; }
    
    public void drawNode(Graphics2D g) {
        label.draw(g);
    }

	public int getNumParameters(int stream){
	    if(dataVect.isEmpty() || dataVect.get(0).isEmpty()) return 0;
		return dataVect.get(0).get(0).getNumParams();
	}

	public int getNumValues(int stream){
	    if(dataVect.isEmpty() || dataVect.get(0).isEmpty()) return 0;
		return dataVect.get(0).get(0).getNumValues();
	}

	public DataElement getElement(int i,int stream) { return dataVect.get(stream).get(i); }

    public int getNumStreams() {
        if(dataVect==null || dataVect.size()<1) return 1;
        return dataVect.size();
    }

	/**
	 * Returns the number of data elements that this source has in it.  I'm using
	 * this instead of an iterator because direct access is much more efficient
	 * when trying to make tables of data.
	 * @return The number of data elements in this source.
	 */
	public int getNumElements(int stream){ return dataVect.get(stream).size(); }
	
    public JComponent getPropertiesPanel() {
        if(propPanel==null) {
            propPanel=new JTabbedPane();
            setupSpecificPanelProperties();
            
			propPanel.addTab("Sources",getSourceInfoPanel());
			propPanel.addTab("Output",getOutputInfoPanel());   
            propPanel.addTab("Label",label.getAreaPanel("Label"));   
        }
        return propPanel;
    }

    public void relink(Hashtable<GraphElement,GraphElement> linkHash) {
        for(int i=0; i<sinkVector.size(); ++i) {
            sinkVector.set(i,(DataSink)linkHash.get(sinkVector.get(i)));
        }
        for(int i=0; i<inputVector.size(); ++i) {
            inputVector.set(i,(DataSource)linkHash.get(inputVector.get(i)));
        }
    }
    
    public void redo() {
    	localRedo();
    }
    
    public String toString() {
        return label.toString();
    }
    
	protected void abstractRedoAllElements() { 
	    GraphPanel.instance().newWorkOrder(this);
	}
    
	protected void localRedo() {
		if(inputVector.isEmpty()) return;
	    if(clearOnRedo()) {
	        for(int i=0; i<dataVect.size(); ++i) {
	            dataVect.get(i).clear();
	        }
	    }
	    if (doingInThreads()) {
	    	redoAllElements();
	    } else {
	    	ThreadHandler.instance().loadWaitTask(this,new Runnable() {
	    		public void run() {
	    			redoAllElements();
	    		}
	    	});
	    	ThreadHandler.instance().waitForAll(this);
	    }
		if(oip!=null) oip.redoOutputTable();
	}
	
	protected void sizeDataVectToInputStreams() {
	    if(dataVect.size()>inputVector.get(0).getNumStreams()) dataVect.clear();
	    while(dataVect.size()<inputVector.get(0).getNumStreams()) dataVect.add(new ArrayList<DataElement>());
	}
    
    protected boolean clearOnRedo() {
        return true;
    }

    protected SourceInfoPanel getSourceInfoPanel() {
        if (sip == null)
            sip = new SourceInfoPanel(this);
        return sip;
    }

    protected OutputInfoPanel getOutputInfoPanel() {
        if (oip == null)
            oip = new OutputInfoPanel(this);
        return oip;
    }

	
	/**
	 * This method is intended to reprocess all the input elements to redo the
	 * output.  It must be properly implemented by all extending classes.
	 */
	protected abstract void redoAllElements();
	

	/**
	 * This method should add any tabbed panes to the propPanel that are specific to
	 * the given filter.  This is called when the propPanel is created.  After it is
	 * called, the source and output info panels are added.
	 */
	protected abstract void setupSpecificPanelProperties();
	
	/**
	 * Method implemented to inform the AbstractFilter whether the Filter is handling
	 * threading threading or if if the AbstractFilter should put the work into the 
	 * ThreadHandler itself.
	 * @return True if redoAllElements() is threaded; False if redoAllElements() should be
	 * threaded by AbstractFilter
	 */
	protected abstract boolean doingInThreads();

    protected ArrayList<ArrayList<DataElement>> dataVect = new ArrayList<ArrayList<DataElement>>();

    protected ArrayList<DataSource> inputVector = new ArrayList<DataSource>();

    protected ArrayList<DataSink> sinkVector = new ArrayList<DataSink>();

    private GraphLabelString label=new GraphLabelString(getDescription(),getPaint());


    protected transient JTabbedPane propPanel;

    private transient SourceInfoPanel sip;

    private transient OutputInfoPanel oip;

    private static final long serialVersionUID=3919257499786698166l;
}
